package cumeo89.chii.client.form;

import static com.google.gwt.event.dom.client.KeyCodes.*;
import static cumeo89.chii.client.entry.Constants.*;
import static cumeo89.chii.client.images.ImageFactory.*;

import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.MenuBar;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.xml.client.Attr;
import com.google.gwt.xml.client.Element;
import com.google.gwt.xml.client.Node;

import cumeo89.chii.client.Chii;
import cumeo89.chii.client.ChiiListener;
import cumeo89.chii.client.entry.Entry;
import cumeo89.chii.client.entry.edit.InsertNodeEdit;
import cumeo89.chii.client.entry.edit.RemoveAttrEdit;
import cumeo89.chii.client.entry.edit.RemoveNodeEdit;
import cumeo89.chii.client.entry.edit.SetAttrEdit;
import cumeo89.chii.client.entry.editor.TextEditorController;
import cumeo89.chii.client.ui.FullWindowPanel;
import cumeo89.chii.client.ui.ImageMenuItem;
import cumeo89.chii.client.ui.Palette;
import cumeo89.chii.client.undo.UndoManager;
import cumeo89.chii.client.undo.UndoableEdit;
import cumeo89.chii.client.undo.UndoableEditListener;
import cumeo89.chii.client.util.AbstractAction;
import cumeo89.chii.client.util.Action;
import cumeo89.chii.client.util.MyChangeListener;
import cumeo89.chii.client.util.XMLUtil;

public class MainForm extends FullWindowPanel implements MyChangeListener,
		UndoableEditListener, ChiiListener {

	private static final int KEY_R = 82;
	private static final int KEY_S = 83;
	private static final int KEY_Z = 90;
	private static final int KEY_Y = 89;

	private SimplePanel deck = new SimplePanel();
	private Entry entry = new Entry();
	private TextArea txtWiktext = new TextArea();
	private TextArea txtXml = new TextArea();
	private Palette palette = new Palette();
	private SimplePanel pnlWysiwyg = new SimplePanel();
	private MenuBar menubar = new MenuBar();
	private SavingDialog dlgSave = null;
	private UndoManager undoManager = new UndoManager();

	public MainForm(Chii chii) {
		super();
		initWidgets();
		sinkEvents(Event.ONKEYUP);
		chii.addChiiListener(this);
	}

	private void initWidgets() {
		Constraint c = new Constraint();

		MenuBar mbEntry = new MenuBar(true);
		mbEntry.addItem(new ImageMenuItem(IMAGES.save(), "Lưu", actSave));
		mbEntry.addItem(new ImageMenuItem(IMAGES.refresh(), "Nạp lại",
				actReload));
		mbEntry.addItem(new ImageMenuItem(IMAGES.exit(), "Thoát", actExit));
		menubar.addItem("Mục từ", mbEntry);

		MenuBar mbView = new MenuBar(true);
		mbView.addItem(new ImageMenuItem(IMAGES.togglePalette(),
				"Ẩn/Hiện palette", actTogglePalette));
		mbView.addSeparator();
		mbView.addItem(new ImageMenuItem(IMAGES.wysiwyg(), "Soạn trực quan",
				actSwitchWysiwyg));
		mbView.addItem(new ImageMenuItem(IMAGES.wikitext(), "Soạn mã Wikitext",
				actSwitchWikitext));
		mbView.addItem(new ImageMenuItem(IMAGES.xml(), "Xem mã XML",
				actSwitchXml));
		menubar.addItem("Hiển thị", mbView);

		MenuBar mbEdit = new MenuBar(true);

		MenuBar mbInsert = new MenuBar(true);
		mbInsert.addItem("Ngôn ngữ", actInsertLanguage);
		mbInsert.addItem("Từ loại", actInsertPos);
		mbInsert.addItem("Định nghĩa", actInsertDefinition);
		mbInsert.addItem("Ngữ cảnh", actInsertContext);
		mbInsert.addItem("Ví dụ", actInsertExample);
		mbInsert.addItem("Giải nghĩa", actInsertExplain);
		mbInsert.addItem("Danh sách con", actInsertSublist);
		mbInsert.addItem("LK liên wiki", actInsertInterwikiLink);
		mbInsert.addItem("Thể loại", actInsertCategory);
		mbEdit.addItem(new ImageMenuItem(IMAGES.add(), "Thêm mới", mbInsert));

		mbEdit.addItem(new ImageMenuItem(IMAGES.edit(), "Chỉnh sửa", actEdit));
		mbEdit.addItem(new ImageMenuItem(IMAGES.remove(), "Xoá", actRemove));
		mbEdit.addSeparator();
		mbEdit
				.addItem(new ImageMenuItem(IMAGES.undo(), "Huỷ thao tác",
						actUndo));
		mbEdit.addItem(new ImageMenuItem(IMAGES.redo(), "Thực hiện lại",
				actRedo));
		menubar.addItem("Soạn thảo", mbEdit);

		MenuBar mbFormating = new MenuBar(true);
		mbFormating.addItem(new ImageMenuItem(IMAGES.bold(), "Đậm", actBold));
		mbFormating.addItem(new ImageMenuItem(IMAGES.italic(), "Nghiêng",
				actItalic));
		mbFormating.addItem(new ImageMenuItem(IMAGES.internalLink(),
				"Lkết trong", actInternalLink));
		mbFormating.addItem(new ImageMenuItem(IMAGES.externalLink(),
				"Lkết ngoài", actExternalLink));
		menubar.addItem("Định dạng", mbFormating);

		menubar.addItem(new ImageMenuItem(IMAGES.help(), "Trợ giúp", actHelp));

		c.height = 35;
		c.direction = DockPanel.NORTH;
		this.add(menubar, c);

		palette.setSupressRefresh(true);
		palette.addItem("Ngôn ngữ", actInsertLanguage);
		palette.addItem("Từ loại", actInsertPos);
		palette.addItem("Định nghĩa", actInsertDefinition);
		palette.addItem("Ngữ cảnh", actInsertContext);
		palette.addItem("Ví dụ", actInsertExample);
		palette.addItem("Giải nghĩa", actInsertExplain);
		palette.addItem("Danh sách con", actInsertSublist);
		palette.addItem("LK liên wiki", actInsertInterwikiLink);
		palette.addItem("Thể loại", actInsertCategory);
		palette.setSupressRefresh(false);

		c.width = 150;
		c.direction = DockPanel.EAST;
		this.add(palette, c);

		pnlWysiwyg.setWidth("100%");
		DOM.setStyleAttribute(pnlWysiwyg.getElement(), "position", "relative");
		DOM.setStyleAttribute(pnlWysiwyg.getElement(), "overflow", "auto");
		entry.addChangeListener(this);
		entry.addUndoableEditListener(this);
		pnlWysiwyg.setWidget(entry);

		txtWiktext.setWidth("100%");
		TextEditorController.getInstance().registerText(txtWiktext);
		TextEditorController.getInstance().addChangeListener(this);
		onChanged(TextEditorController.getInstance());

		txtXml.setWidth("100%");
		txtXml.setReadOnly(true);

		c.width = 400;
		c.height = 350;
		c.direction = DockPanel.CENTER;
		this.add(deck, c);
	}

	/*
	 * Listeners
	 */
	@Override
	protected void doLayout() {
		super.doLayout();
		if (deck.getWidget() != null) {
			deck.getWidget().setHeight(
					DOM.getStyleAttribute(deck.getElement(), "height"));
		}
	}

	@Override
	public void onBrowserEvent(Event event) {
		if (processBrowserEvent(event)) {
			event.stopPropagation();
			event.preventDefault();
		} else {
			super.onBrowserEvent(event);
		}
	}

	private boolean processBrowserEvent(Event evt) {
		if (DOM.eventGetType(evt) == Event.ONKEYUP) {
			int key = evt.getKeyCode();
			if (key == KEY_S && !evt.getCtrlKey() && pnlWysiwyg.isVisible()
					&& entry.getSelectedNode() != null) {
				entry.edit(entry.getSelectedNode());
				return true;
			}
			if (key == KEY_S && evt.getAltKey() && evt.getShiftKey()) {
				actSave.execute();
				return true;
			}
			if (key == KEY_R && evt.getCtrlKey()) {
				actReload.execute();
				return true;
			}
			if (key == KEY_DELETE && entry.isVisible()
					&& entry.getSelectedNode() != null) {
				actRemove.execute();
				return true;
			}
			if (key == KEY_Z && evt.getCtrlKey()) {
				actUndo.execute();
				return true;
			}
			if (key == KEY_Y && evt.getCtrlKey()) {
				actRedo.execute();
				return true;
			}
		}
		return false;
	}

	public void onChanged(Object sender) {
		if (sender == TextEditorController.getInstance()) {
			boolean enabled = TextEditorController.getInstance()
					.getControlledText() != null;
			actBold.setEnabled(enabled);
			actItalic.setEnabled(enabled);
			actInternalLink.setEnabled(enabled);
			actExternalLink.setEnabled(enabled);
		} else if (sender == entry) {
			actRemove.setEnabled(entry.getSelectedNode() != null);
		} else if (sender == deck) {
			actEdit.setEnabled(pnlWysiwyg.isVisible());
			actRemove.setEnabled(pnlWysiwyg.isVisible());
		}
	}

	public void chiiShutedDown(Chii chii) {
	}

	public String chiiWillShutDown(Chii chii) {
		return "Nạp lại sẽ làm mất toàn bộ sửa đổi vừa qua.\n"
				+ "Bạn có chắc muốn thực hiện?";
	}

	public void entryLoaded(Chii chii) {
		entry.setModel(chii.getModel());
		show(pnlWysiwyg);
	}

	private void show(final Widget w) {
		deck.setWidget(w);
		doLayout();
	}

	/*
	 * Utility methods
	 */
	private Element searchUp(String tag) {
		return searchUp(entry.getSelectedNode(), tag);
	}

	private Element searchUp(Node node, String tag) {
		if (node == null) {
			return null;
		}
		if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
			return searchUp(XMLUtil.getElement((Attr) node), tag);
		}
		if (node.getNodeType() == Node.ELEMENT_NODE
				&& tag.equals(((Element) node).getTagName())) {
			return (Element) node;
		}
		return searchUp(node.getParentNode(), tag);
	}

	private Element ensureExist(Element parent, String tag) {
		if (parent.getElementsByTagName(tag).getLength() <= 0) {
			entry.getModel().appendChild(parent,
					entry.getDocument().createElement(tag));
		}
		return (Element) parent.getElementsByTagName(tag).item(0);
	}

	/*
	 * Actions
	 */
	private final Action actBold = new AbstractAction() {

		public void executeImpl() {
			TextEditorController.getInstance().bold();
		}
	};

	private final Action actItalic = new AbstractAction() {

		public void executeImpl() {
			TextEditorController.getInstance().italic();
		}
	};

	private final Action actInternalLink = new AbstractAction() {

		public void executeImpl() {
			TextEditorController.getInstance().internallink();
		}
	};

	private final Action actExternalLink = new AbstractAction() {

		public void executeImpl() {
			TextEditorController.getInstance().externalLink();
		}
	};

	private final Action actHelp = new AbstractAction() {

		public void executeImpl() {
			Window.open("http://code.google.com/p/chii/wiki/HelpPage",
					"_blank", "menubar =no,toolbar=no,status=no");
		}
	};

	private final Action actRedo = new AbstractAction() {

		public void executeImpl() {
			undoManager.redo();
			updateUndoRedo();
		}
	};

	private final Action actUndo = new AbstractAction() {

		public void executeImpl() {
			undoManager.undo();
			updateUndoRedo();
		}
	};

	private final Action actEdit = new AbstractAction() {

		public void executeImpl() {
			Node toEdit = entry.getSelectedNode();
			if (XMLUtil.hasTag(toEdit, E_DEF)
					|| XMLUtil.hasTag(toEdit, E_EXAMPLE)) {
				toEdit = ((Element) toEdit).getAttributeNode(A_DEF_CONTENT);
			}
			if (XMLUtil.hasTag(toEdit, E_LANG) || XMLUtil.hasTag(toEdit, E_POS)) {
				toEdit = ((Element) toEdit).getAttributeNode("code");
			}
			entry.edit(toEdit);
		}
	};

	private final Action actRemove = new AbstractAction() {

		public void executeImpl() {
			Node node = entry.getSelectedNode();
			if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
				Attr attr = (Attr) node;
				String name = attr.getName();
				if ("content".equals(name) || A_INTERWIKI_LANG.equals(name)
						|| A_CATEGORY_NAME.equals(name)) {
					node = XMLUtil.getElement(attr);
				}
			}

			if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
				performEdit(new RemoveAttrEdit(entry.getModel(), (Attr) node));
			} else {
				performEdit(new RemoveNodeEdit(entry.getModel(), node));
			}
		}
	};

	private final Action actInsertCategory = new AbstractAction() {

		public void executeImpl() {
			Element cat = entry.getDocument().createElement(E_CATEGORY);
			cat.setAttribute(A_CATEGORY_NAME, "Thể loại");
			performEdit(new InsertNodeEdit(entry.getModel(), ensureExist(entry
					.getRoot(), E_CATEGORY_LIST), cat, searchUp(E_CATEGORY)));
			entry.edit(cat.getAttributeNode(A_CATEGORY_NAME));
		}
	};

	private final Action actInsertInterwikiLink = new AbstractAction() {

		public void executeImpl() {
			Element inter = entry.getDocument().createElement(E_INTERWIKI);
			inter.setAttribute(A_INTERWIKI_LANG, "");
			performEdit(new InsertNodeEdit(entry.getModel(), ensureExist(entry
					.getRoot(), E_INTERWIKI_LIST), inter, searchUp(E_INTERWIKI)));
			entry.edit(inter.getAttributeNode(A_INTERWIKI_LANG));
		}
	};

	private final Action actInsertSublist = new AbstractAction() {

		public void executeImpl() {
			Element list = searchUp(E_RELATED);
			if (list != null) {
				Element sublist = entry.getDocument().createElement(E_SUBLIST);
				sublist.setAttribute(A_SUBLIST_TITLE, "Tiêu đề");
				performEdit(new InsertNodeEdit(entry.getModel(), list, sublist,
						searchUp(E_SUBLIST)));
				entry.edit(sublist.getAttributeNode(A_SUBLIST_TITLE));
			}
		}
	};

	private final Action actInsertExplain = new AbstractAction() {

		public void executeImpl() {
			Element ex = searchUp(E_EXAMPLE);
			if (ex != null && !ex.hasAttribute(A_EXAMPLE_EXPLAIN)) {
				performEdit(new SetAttrEdit(entry.getModel(), ex,
						A_EXAMPLE_EXPLAIN, "Giải nghĩa"));
				entry.edit(ex.getAttributeNode(A_EXAMPLE_EXPLAIN));
			}
		}
	};

	private final Action actInsertExample = new AbstractAction() {

		public void executeImpl() {
			Element def = searchUp(E_DEF);
			if (def != null) {
				final Element ex = entry.getDocument().createElement(E_EXAMPLE);
				ex.setAttribute("content", "Ví dụ");
				performEdit(new InsertNodeEdit(entry.getModel(), ensureExist(
						def, E_EXAMPLE_LIST), ex, searchUp(E_EXAMPLE)));
				entry.edit(ex.getAttributeNode("content"));
			}
		}
	};

	private final Action actInsertDefinition = new AbstractAction() {

		public void executeImpl() {
			Element pos = searchUp(E_POS);
			if (pos != null) {
				Element def = entry.getDocument().createElement(E_DEF);
				def.setAttribute(A_DEF_CONTENT, "Định nghĩa");
				performEdit(new InsertNodeEdit(entry.getModel(), ensureExist(
						pos, E_DEF_LIST), def, searchUp(E_DEF)));
				entry.edit(def.getAttributeNode(A_DEF_CONTENT));
			}
		}
	};

	private final Action actInsertContext = new AbstractAction() {

		public void executeImpl() {
			Element def = searchUp(E_DEF);
			if (def != null) {
				final Element con = entry.getDocument()
						.createElement(E_CONTEXT);
				con.setAttribute(A_CONTEXT_TEXT, "Ngữ cảnh");
				performEdit(new InsertNodeEdit(entry.getModel(), ensureExist(
						def, E_CONTEXT_LIST), con, searchUp(E_CONTEXT)));
				entry.edit(con.getAttributeNode(A_CONTEXT_TEXT));
			}
		}
	};

	private final Action actInsertPos = new AbstractAction() {

		public void executeImpl() {
			Element lang = searchUp(E_LANG);
			if (lang != null) {
				Element pos = entry.getDocument().createElement(E_POS);
				pos.setAttribute("code", " ");
				performEdit(new InsertNodeEdit(entry.getModel(), lang, pos,
						searchUp(E_POS)));
				entry.edit(pos.getAttributeNode("code"));
			}
		}
	};

	private final Action actInsertLanguage = new AbstractAction() {

		public void executeImpl() {
			Element lang = entry.getDocument().createElement(E_LANG);
			lang.setAttribute(A_LANG_CODE, " ");
			performEdit(new InsertNodeEdit(entry.getModel(), ensureExist(entry
					.getRoot(), E_LANG_LIST), lang, null));
			entry.edit(lang.getAttributeNode(A_LANG_CODE));
		}

	};

	private final Action actSave = new AbstractAction() {

		public void executeImpl() {
			if (dlgSave == null) {
				dlgSave = new SavingDialog();
				dlgSave.addCloseHandler(new CloseHandler<PopupPanel>() {
					public void onClose(CloseEvent<PopupPanel> event) {
						if (dlgSave.isSaved()) {
							actExit.execute();
						}
					}
				});
			}
			Chii.get().getPage().setContent(
					txtWiktext.isVisible() ? txtWiktext.getText() : Chii.get()
							.getMediaWiki().toWikitext(entry.getDocument()));
			dlgSave.setPage(Chii.get().getPage());
			dlgSave.center();
		}
	};

	private final Action actExit = new AbstractAction() {

		public void executeImpl() {
			Chii.get().shutdown();
		}
	};

	private final Action actSwitchWysiwyg = new AbstractAction() {

		public void executeImpl() {
			if (pnlWysiwyg.getParent() != null) {
				return;
			}
			if (txtWiktext.getParent() != null) {
				Chii.get().getPage().setContent(txtWiktext.getText());
				Chii.get().textToModel();
			}
			show(pnlWysiwyg);
		}
	};

	private final Action actSwitchWikitext = new AbstractAction() {

		public void executeImpl() {
			if (txtWiktext.getParent() != null) {
				return;
			}
			Chii.get().modelToText();
			txtWiktext.setText(Chii.get().getPage().getContent());
			show(txtWiktext);
		}
	};

	private final Action actSwitchXml = new AbstractAction() {

		public void executeImpl() {
			if (txtXml.getParent() != null) {
				return;
			}
			if (txtWiktext.getParent() != null) {
				Chii.get().getPage().setContent(txtWiktext.getText());
				Chii.get().textToModel();
			}
			txtXml.setText(XMLUtil.toPrettyString(entry.getDocument()));
			show(txtXml);
		}
	};

	private final Action actReload = new AbstractAction() {

		@Override
		protected void executeImpl() {
			if (Window.confirm("Nạp lại sẽ làm mất toàn bộ sửa đổi vừa qua.\n"
					+ "Bạn có chắc muốn thực hiện?")) {
				Chii.get().reload(null);
			}
		}
	};

	private final Action actTogglePalette = new AbstractAction() {

		@Override
		protected void executeImpl() {
			palette.setVisible(!palette.isVisible());
		}
	};

	/*
	 * Undo/Redo
	 */
	private void performEdit(UndoableEdit edit) {
		edit.redo();
		undoManager.add(edit);
		updateUndoRedo();
	}

	public void undoableEditHappened(Object source, UndoableEdit edit) {
		if (source == entry) {
			undoManager.add(edit);
			updateUndoRedo();
		}
	}

	private void updateUndoRedo() {
		actUndo.setEnabled(undoManager.canUndo());
		actRedo.setEnabled(undoManager.canRedo());
	}

}
